package com.zf.common.http.rest_template;

import com.zf.common.core.constants.BasicConstants;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;
import java.time.Duration;
import java.util.Objects;

/**
 * RestTemplate配置：
 * <pre>
 * 定义 restTemplate: 推荐方式2
 *     方式1：手动 `RestTemplate restTemplate = new RestTemplate()`，相比于 方式2 会少一些 Message 转换器，获取请求的响应内容时可能会乱码，乱码时需要获取二进制 byte[], 然后手动指定编码后再去转换（new String(byte[], StandardCharsets.UTF_8)）。
 *     方式2：注入 RestTemplateBuilder 对象，然后通过 restTemplateBuilder.build() 创建 RestTemplate，会额外注入一些 Message 转换器，获取请求的响应内容时一般不会出现乱码。
 * </pre>
 * <pre>
 * 官方文档：
 *      https://docs.spring.io/spring-framework/reference/6.1/integration/rest-clients.html#rest-resttemplate
 *      https://docs.spring.io/spring-boot/docs/3.2.12/reference/html/io.html#io.rest-client.resttemplate
 * </pre>
 * 使用样例: {@link com.zf.common.http.RestTemplateTests}
 */
@Configuration
public class RestTemplateConfig {

    /**
     * 定义根据URL调用的RestTemplate，只能通过uri调用服务。
     * <pre>
     * {@code
     *     // 1：直接通过uri调用
     *     String res = restTemplateByUri.getForObject("http://localhost:8818/test/hello", String.class);
     *
     *     // 2：手动选择服务
     *     @Autowired
     *     private LoadBalancerClient loadBalancerClient;
     *
     *     ServiceInstance serviceInstance = loadBalancerClient.choose("gateway-downstream");
     *     // 拼接URL
     *     String url = String.format("http://%s:%s/test/hello", serviceInstance.getHost(), serviceInstance.getPort());
     *     String res = restTemplateByUri.getForObject(url, String.class);
     * }
     * </pre>
     */
    @Bean("restTemplateByUri")
    public RestTemplate restTemplateByUri(RestTemplateBuilder restTemplateBuilder) {

        // 自定义请求工厂
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        // 设置连接和读取超时，默认超时时间是无限大的
        // 连接超时：是指客户端尝试与服务器建立连接时等待的最大时间。如设置连接超时为 5 秒，那么客户端在 5 秒内无法与服务器建立连接时，会抛出超时异常
        // 读取超时：是指客户端在连接已经建立后，等待服务器返回数据时的最大时间。如设置读取超时为 10 秒，那么客户端在连接建立后，如果在 10 秒内没有收到服务器的响应数据，会抛出超时异常
        requestFactory.setConnectTimeout(Duration.ofSeconds(5));
        requestFactory.setReadTimeout(Duration.ofSeconds(10));

        // 方式 1：
        // RestTemplate restTemplate = new RestTemplate();
        // // 添加请求拦截器
        // restTemplate.getInterceptors().add(new TraceIdRestTemplateRequestInterceptor());
        // // 设置自定义的请求工厂
        // restTemplate.setRequestFactory(requestFactory);
        // return restTemplate;

        // 方式 2：RestTemplateBuilder 每次操作都会生成一个新的对象，需要重新赋值给 builder
        // 添加请求拦截器
        restTemplateBuilder = restTemplateBuilder.additionalInterceptors(new TraceIdRestTemplateRequestInterceptor());
        // 设置自定义的请求工厂
        // restTemplateBuilder = restTemplateBuilder.requestFactory(() -> requestFactory);
        return restTemplateBuilder.build();
    }

    /**
     * 定义根据服务名调用的RestTemplate，实现了负载均衡，只能根据服务名调用服务。
     * <pre>
     * 加 LoadBalanced 注解后，该 RestTemplate 会自动使用使用服务发现和负载均衡，如果用的是nacos，则使用nacos作为服务发现中心。
     * 添加了 LoadBalanced 注解后，就不能通过uri调用服务了。
     * </pre>
     * <p>
     * {@link LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#restTemplateCustomizer(LoadBalancerInterceptor) } 自动配置类内部逻辑，
     * 对添加了 LoadBalanced 注解的 RestTemplate 会自动注入一个 LoadBalancerInterceptor 拦截器，
     * 其内部实现了自动根据 service-id 获取地址的逻辑。
     *
     * <pre>
     * {@code
     *     // 通过服务名调用
     *     String res = restTemplateByServiceId.getForObject("http://service-id/test/hello", String.class);
     * }
     * </pre>
     */
    @LoadBalanced
    @Bean("restTemplateByServiceId")
    public RestTemplate restTemplateByServiceId(RestTemplateBuilder restTemplateBuilder) {
        // RestTemplateBuilder 每次操作都会生成一个新的对象，需要重新赋值给 builder
        restTemplateBuilder = restTemplateBuilder.additionalInterceptors(new TraceIdRestTemplateRequestInterceptor());
        return restTemplateBuilder.build();
    }

    /**
     * 自定义 RestTemplate 的请求拦截器，可以定义多个，都会生效
     */
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public static class TraceIdRestTemplateRequestInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (Objects.nonNull(requestAttributes)) {
                // 从当前的请求中获取请求头 TRACE_ID
                HttpServletRequest orgRequest = requestAttributes.getRequest();
                String traceId = orgRequest.getHeader(BasicConstants.TRACE_ID);
                if (!StringUtils.hasText(traceId)) {
                    // 如果没有在请求头中获取 TRACE_ID，则去 request的Attribute 中取 TRACE_ID
                    Object attribute = orgRequest.getAttribute(BasicConstants.TRACE_ID);
                    traceId = Objects.isNull(attribute) ? null : String.valueOf(attribute);
                }
                if (StringUtils.hasText(traceId) && !request.getHeaders().containsKey(BasicConstants.TRACE_ID)) {
                    // 给 RestTemplate的请求 设置 TRACE_ID 请求头
                    request.getHeaders().set(BasicConstants.TRACE_ID, traceId);
                }
            }
            // 由 org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute 实现请求的发送，
            // 该方法先会执行所有的 ClientHttpRequestInterceptor#intercept，直到执行完最后一个 ClientHttpRequestInterceptor后，才会真正发送请求
            return execution.execute(request, body);
        }
    }
}
